package jrain.fw.dao.druid;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import com.alibaba.druid.filter.Filter;
import com.alibaba.druid.filter.logging.Slf4jLogFilter;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;

import jrain.fw.core.cfg.CfgCallback;
import jrain.fw.core.cfg.CfgService;
import jrain.fw.core.dao.DataSourceService;
import jrain.fw.core.log.Logger;
import jrain.fw.core.log.LoggerFactory;
import jrain.fw.core.service.DestroyService;
import jrain.fw.core.service.InitService;
import jrain.fw.core.service.Service;
import jrain.fw.core.service.ServiceConst;
import jrain.fw.core.utils.FwUtils;
import jrain.utils.collection.CollectionUtils;
import jrain.utils.collection.RMap;
import jrain.utils.exception.ServiceException;
import jrain.utils.lang.StringUtils;

/**
 * <pre>
 * 作者：3244924214@qq.com 
 * 描述：数据源配置
 * </pre>
 */
@Service(name = "druid", group = ServiceConst.S_DAO_DATASOURCE_SERVICE, module = ServiceConst.M_DAO)
public class DruidDataSourceService implements DataSourceService, InitService, DestroyService {

	private static final Logger log = LoggerFactory.getLogger("dao-druid");
	private static final AtomicBoolean init = new AtomicBoolean(false);
	private static final Map<String, DruidDataSource> dataSourceMap = new HashMap<String, DruidDataSource>();
	private static final Map<String, CfgCallback> callbackMap = new HashMap<>();
	private static final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
	private static final Object eventLock = new Object();

	public void init() {
		if (!init.getAndSet(true)) {
			RMap<String, Object> jdbcMap = FwUtils.getCfgService().getCfgMap(ServiceConst.M_DAO, null);
			if (jdbcMap != null) {
				Set<String> set = CollectionUtils.valueOfSet(StringUtils.trimNull(jdbcMap.get("jdbc.names")));
				for (String jdbcName : set) {
					initDataSouce(jdbcName, true);
				}
				dataSouceChange(set);
			}
			log.info("{} druid started!", ServiceConst.S_DAO_DATASOURCE_SERVICE);
		}
	}

	public void destroy() {
		if (init.get()) {
			CfgService cfg = FwUtils.getCfgService();
			lock.writeLock().lock();
			try {
				Set<String> jdbcNames = dataSourceMap.keySet();
				for (String jdbcName : jdbcNames) {
					closeDataSouce(jdbcName);
					String moduleName = ServiceConst.M_DAO + "." + jdbcName;
					cfg.unregisterCfgCallBack(moduleName);
				}
				callbackMap.clear();
				dataSourceMap.clear();
			} finally {
				lock.writeLock().unlock();
			}
			log.info("{} druid stoped!", ServiceConst.S_DAO_DATASOURCE_SERVICE);
			init.set(false);
		}
	}

	protected void initDataSouce(String jdbcName, boolean bthrow) {
		try {
			CfgService cfgService = FwUtils.getCfgService();
			String moduleName = ServiceConst.M_DAO + "." + jdbcName;
			RMap<String, Object> tempMap = cfgService.getCfgMap(moduleName, null);
			DruidDataSource druidDataSource = null;
			if (tempMap != null) {
				Map<String, Object> configMap = new HashMap<>();
				tempMap.keySet().forEach(key -> {
					configMap.put(key, tempMap.get(key));
				});
				druidDataSource = new DruidDataSource();
				List<Filter> filters = new ArrayList<>();
				Slf4jLogFilter filter = new Slf4jLogFilter();
				filter.setStatementExecutableSqlLogEnable(true);
				filters.add(filter);
				druidDataSource.setProxyFilters(filters);
				DruidDataSourceFactory.config(druidDataSource, configMap);
			}
			lock.writeLock().lock();
			try {
				closeDataSouce(jdbcName);
				dataSourceMap.put(jdbcName, druidDataSource);
				CfgCallback callback = callbackMap.remove(jdbcName);
				if (callback == null) {
					callback = new CfgCallback() {
						@Override
						public void callbak(String module, RMap<String, Object> map) {
							synchronized (eventLock) {
								initDataSouce(jdbcName, false);
							}
						}
					};
					callbackMap.put(jdbcName, callback);
					cfgService.registerCfgCallBack(moduleName, callback);
				}
			} finally {
				lock.writeLock().unlock();
			}

			if (tempMap != null) {
				log.info("[dataSource]already open,jdbcName=" + jdbcName);
			} else {
				log.info("[dataSource]configuration is empty,already initialized,jdbcName=" + jdbcName);
			}

		} catch (Exception e) {
			String msg = "[dataSource]cannot create DataSource error! jdbcName=" + jdbcName;
			log.error(msg, e);
			if (bthrow) {
				throw new ServiceException(msg, e);
			}
		}
	}

	private void closeDataSouce(String jdbcName) {
		try {
			DruidDataSource ds = dataSourceMap.get(jdbcName);
			if (ds != null) {
				ds.close();
			}
			log.info("[dataSource]already closed,jdbcName=" + jdbcName);
		} catch (Throwable t) {
			log.error("[dataSource]close Error " + jdbcName, t);
		}
	}

	protected void dataSouceChange(Set<String> set) {
		CfgService cfgService = FwUtils.getCfgService();
		cfgService.registerCfgCallBack("jdbc", new CfgCallback() {
			@Override
			public void callbak(String module, RMap<String, Object> map) {
				synchronized (eventLock) {
					lock.writeLock().lock();
					try {
						Set<String> tset = CollectionUtils.valueOfSet(StringUtils.trimNull(map.get("dataSourceNames")));
						Set<String> keySet = dataSourceMap.keySet();
						// 发现增项
						for (String jdbcName : tset) {
							if (!keySet.contains(jdbcName)) {
								initDataSouce(jdbcName, false);

							}
						}
						// 删除废除项
						for (String jdbcName : keySet) {
							if (!tset.contains(jdbcName)) {
								closeDataSouce(jdbcName);
								dataSourceMap.remove(jdbcName);
								callbackMap.remove(jdbcName);
								String moduleName = ServiceConst.M_DAO + "." + jdbcName;
								cfgService.unregisterCfgCallBack(moduleName);
							}
						}

					} finally {
						lock.writeLock().unlock();
					}
				}
			}
		});
	}

	public Connection getConnection(String dataSourceName, Map<String, Object> map) {
		try {
			DruidDataSource dataSource = null;
			lock.readLock().lock();
			try {
				dataSource = dataSourceMap.get(dataSourceName);
			} finally {
				lock.readLock().unlock();
			}
			if (dataSource == null) {
				String msg = "cannot get connection,dataSource=%s is null !";
				msg = String.format(msg, dataSourceName);
				throw new ServiceException(msg);
			}
			return dataSource.getConnection();
		} catch (SQLException e) {
			String msg = "cannot get connection,dataSource=%s error !";
			msg = String.format(msg, dataSourceName);
			throw new ServiceException(msg, e);
		}
	}

}
